算法选错的五个工程现场

五个在实际工程中因为问题建模或算法选择出错而踩坑的场景——每个都能回溯到一个可避免的判断失误

本页目录

花了两周优化一个 NP-hard 问题

物流团队需要给 200 个配送点规划最优路线,最少车辆、最短总距离。开发接了需求,先写了一个贪心算法,结果不理想。换了模拟退火,好一点但不稳定。又试了遗传算法,效果时好时差。

两周后有人问了一句:这个问题有没有精确解?查了一下——这是带约束的车辆路径问题(VRP),NP-hard。不存在"再优化一下就行了"的解法。

正确的处理方式:先识别问题的计算复杂度。确认是 NP-hard 之后,直接选择工业级的近似求解器(如 Google OR-Tools),不要自己从零写启发式算法。用现成工具的效果通常比手写好——因为这些工具已经被大量实际场景打磨过了。

两周的时间本可以在第一天就省下来。

用了哈希表但忘了冲突代价

用户搜索功能需要在 500 万条记录中做精确匹配。开发选了哈希表,期望 O(1) 查找。上线后发现部分查询异常慢——99.9% 的查询 1ms,但 0.1% 的查询要 500ms。

排查发现:某些键的哈希值集中碰撞了。哈希表在这些桶上退化成了链表,查找复杂度从 O(1) 变成了 O(n)。

O(1) 是期望复杂度,不是保证。在需要最坏情况性能保证的场景(如实时系统),平衡 BST 的 O(log n) 最坏保证比哈希表的 O(1) 期望更可靠。或者换一个更好的哈希函数,把冲突率压下去。

算法选型不能只看平均情况。你的 SLA 是按平均响应定义的还是按 P99 定义的?这个回答决定了你该看平均复杂度还是最坏复杂度。

暴力解法其实够用但没人敢用

推荐系统需要从 1000 个候选商品里选出 10 个最匹配的。团队花了三天实现了一个基于 KD-tree 的近似最近邻搜索。

后来有人用暴力遍历跑了一下:1000 个商品全算一遍,3ms。加上网络开销,用户感知不到差别。KD-tree 在这个数据量下没有速度优势,但引入了额外的索引维护复杂度。

暴力解法的坏名声来自算法课上"n 很大"的假设。但工程中的 n 经常没那么大。1000 个候选、10000 个用户、100 条规则——在这些量级上,O(n²) 可能跑得比 O(n log n) 的复杂实现更快,因为代码更简单、缓存更友好。

做算法选型之前先问一个问题:我的 n 是多少?

把排序问题建模成了搜索问题

日志分析系统需要找出"最近一小时内出现次数最多的 10 个错误类型"。开发的第一反应是维护一个实时的 top-K 数据结构(堆)。

实现起来不简单:需要处理时间窗口滑动、需要高效地删除过期条目、需要在并发环境下保证一致性。开发花了一周,效果还不稳定。

换一种思路:每分钟把日志按错误类型聚合一次(用哈希表计数),然后对计数排序取前 10。排序 100 个错误类型的计数值,排序算法哪个都行,1ms 之内搞定。

问题从"实时维护 top-K 的流式计算"变成了"每分钟跑一次批量统计"。延迟增加了最多 1 分钟,但实现复杂度降了一个数量级。

很多时候换一种问题定义比换一种算法更有效。

图算法选对了但图建错了

社交平台要实现"推荐可能认识的人"——和你的朋友的朋友,但不是你的直接朋友。标准的二度关系查询,用 BFS 遍历两层就行。

开发用邻接矩阵存图。10 万用户,邻接矩阵是 10 万 × 10 万 = 100 亿个布尔值。内存放不下。

换成邻接表:每个用户只存自己的好友列表。平均每人 200 个好友,总共 2000 万条边。内存占用从 100 亿降到 2000 万。BFS 的时间复杂度也从 O(V²) 降到了 O(V+E)。

算法选对了,数据结构选错了。图的表示方式(邻接矩阵 vs 邻接表)决定了算法的实际性能。稀疏图用邻接表,稠密图用邻接矩阵——这个判断需要先知道图的密度。

同分类继续看